package il.technion.cs236369.parsers;

import il.technion.cs236369.common.Common;
import il.technion.cs236369.exceptions.ParsingException;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.json.JSONArray;
import org.json.JSONObject;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * Parses an xml to a JSON object
 * 
 * @author Gal Lalouche
 * 
 */
public class XMLToJson extends Parser<JSONObject> {

	@Override
	public JSONObject parse(InputStream stream) throws ParsingException {
		if (stream == null) {
			throw new IllegalArgumentException("xmlStream is null");
		}

		try {
			JSONObject result = new JSONObject();
			DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
			Document doc = docBuilder.parse(stream);
			NodeList childNodes = doc.getChildNodes();
			Node rootNode = childNodes.item(0);
			// find the first actual root node
			for (int i = 1; rootNode.getNodeType() == Node.DOCUMENT_TYPE_NODE; i++) {
				Common.getOut().println("i: " + i + ", rootNodeType: " + rootNode.getNodeType());
				rootNode = childNodes.item(i);
				assert rootNode != null;
			}
			assert rootNode != null;
			JSONObject rootObject = new JSONObject();
			result.put(rootNode.getNodeName(), rootObject);
			handleChildren(rootNode, rootObject);
			return result;
		} catch (ParserConfigurationException | SAXException | IOException e) {
			throw new ParsingException(e);
		}

	}

	private static void handleChildren(Node node, JSONObject rootObject) {
		handleAttributes(node, rootObject);
		NodeList childNodes = node.getChildNodes();
		// iterate over children
		for (int i = 0; i < childNodes.getLength(); i++) {
			Node childNode = childNodes.item(i);
			// handle children recursively
			if (childNode.getNodeType() == Node.ELEMENT_NODE) {
				handleSubElement(rootObject, childNode);
			} else if (childNode.getNodeType() != Node.COMMENT_NODE) {
				handleText(rootObject, childNode);
			}
		}
	}

	private static void handleText(JSONObject rootObject, Node childNode) {
		String text = childNode.getNodeValue().trim();
		if (text.isEmpty() == false) {
			Common.getOut().println(text);
			rootObject.put("#text", text);
		}
	}

	private static void handleSubElement(JSONObject rootObject, Node childNode) {
		String nodeName = childNode.getNodeName();
		String arrayName = nodeName + "Array";
		Common.getOut().println(nodeName + ": ");
		JSONObject existingObject = rootObject.optJSONObject(nodeName);
		JSONArray existingArray = rootObject.optJSONArray(arrayName);
		if (existingObject != null) {
			Common.getOut().println("Existing object");
			// object already exists, change to array
			JSONArray array = new JSONArray();
			array.put(existingObject);
			JSONObject subObject = getNewSubObject(childNode);
			array.put(subObject);
			rootObject.put(arrayName, array);
			rootObject.remove(nodeName);
		} else if (existingArray != null) {
			Common.getOut().println("Existing array");
			JSONObject subObject = getNewSubObject(childNode);
			existingArray.put(subObject);
		} else {
			// new entry
			Common.getOut().println("new object");
			JSONObject subObject = getNewSubObject(childNode);
			rootObject.put(childNode.getNodeName(), subObject);
		}
	}

	private static JSONObject getNewSubObject(Node childNode) {
		JSONObject subObject = new JSONObject();
		handleChildren(childNode, subObject);
		return subObject;
	}

	private static void handleAttributes(Node node, JSONObject rootObject) {
		NamedNodeMap attributes = node.getAttributes();
		if (attributes == null) {
			return;
		}

		for (int i = 0; i < attributes.getLength(); i++) {
			Node attribute = attributes.item(i);
			rootObject.put("-" + attribute.getNodeName(), attribute.getNodeValue());
		}
	}

	public static void main(String[] args) throws Exception {
		System.out.println(new XMLToJson().parse(new File("C:/Users/gal/youhoo/HW4/config.xml")));
	}
}
